Fix URL change detection broken by database search and replace#13135
Fix URL change detection broken by database search and replace#13135shervElmi wants to merge 8 commits into
Conversation
🎭 Playwright reports for e35bb46: 📦 Build files for e35bb46:
|
Store the setting base64-encoded, so a database search and replace leaves it alone and URL change detection keeps working. The getter decodes on read, and a value an earlier version stored in plain text passes through unchanged.
Re-save the connected proxy URL on admin_init, so a value an earlier version stored in plain text becomes encoded. The migration runs once and then stamps the DB version option.
Add the migration to the plugin's registration list, so it runs alongside the earlier migrations.
ef75334 to
984a68f
Compare
Rephrase comments to enhance understanding of how legacy values are stored and processed. This improves code readability and maintainability.
tofumatt
left a comment
There was a problem hiding this comment.
I know the original IB mentioned supporting unencoded URLs… but why?
I've asked @aaemnnosttv about this since he wrote the issue/ACs. But if that isn't needed I think we can simplify this implementation 🙂
| * @since n.e.x.t Compares against the decoded setting value. | ||
| * | ||
| * @param string $url URL to match against the current one in the settings. | ||
| * @param string $site_url URL to match against the current one in the settings. | ||
| * @return bool TRUE if URL matches the current one, otherwise FALSE. | ||
| */ | ||
| public function matches_url( $url ) { | ||
| $sanitize = $this->get_sanitize_callback(); | ||
| $normalized = $sanitize( $url ); | ||
| return $normalized === $this->get(); | ||
| public function matches_url( $site_url ) { | ||
| return trailingslashit( $site_url ) === $this->get(); | ||
| } |
There was a problem hiding this comment.
Why change the argument name here? We're still just comparing a URL; the function doesn't require it be the site URL so this is just slightly confusing to me.
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param string $value Connected proxy URL, either plain text or encoded. |
There was a problem hiding this comment.
I know the IB mentions supporting plaintext, but do we really need to do that in the set method? Seems like we could just have a migration for this data instead, and then simplify our actual code here to not have to check for https?:\/\/ at the start of the URL… 🤔
| * The encoded value holds no readable URL, so a database search and | ||
| * replace leaves it unchanged. An already encoded value decodes first, | ||
| * so a re-save never encodes it twice. |
There was a problem hiding this comment.
| * The encoded value holds no readable URL, so a database search and | |
| * replace leaves it unchanged. An already encoded value decodes first, | |
| * so a re-save never encodes it twice. | |
| * We encode the URL to prevent database-wide search-and-replace tasks | |
| * from changing the URL used to connect to the Site Kit Proxy service. |
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param string $value Connected proxy URL, either plain text or encoded. |
There was a problem hiding this comment.
Similarly… why would we ever pass an already-encoded value to this function? 🤔
There was a problem hiding this comment.
If we already have this migration, why do we need to account for both encoded and unencoded values in the Connected_Proxy_URL file? 🤔
Seems we can assume they're always encoded, no?
| public function test_get__returns_a_legacy_plain_text_url() { | ||
| $connected_proxy_url = new Connected_Proxy_URL( $this->options ); | ||
|
|
||
| // Store the option in plain text, the way earlier plugin versions saved it. | ||
| $this->update_option( 'https://example.com/' ); | ||
|
|
||
| $connected_proxy_url->register(); | ||
|
|
||
| $this->assertEquals( 'https://example.com/', $connected_proxy_url->get(), 'Getter should return a legacy plain text value unchanged.' ); | ||
| } | ||
|
|
There was a problem hiding this comment.
| public function test_get__returns_a_legacy_plain_text_url() { | |
| $connected_proxy_url = new Connected_Proxy_URL( $this->options ); | |
| // Store the option in plain text, the way earlier plugin versions saved it. | |
| $this->update_option( 'https://example.com/' ); | |
| $connected_proxy_url->register(); | |
| $this->assertEquals( 'https://example.com/', $connected_proxy_url->get(), 'Getter should return a legacy plain text value unchanged.' ); | |
| } |
As mentioned, I think we can remove this.
| public function test_get__returns_a_value_that_fails_to_decode_unchanged() { | ||
| $connected_proxy_url = new Connected_Proxy_URL( $this->options ); | ||
|
|
||
| $this->update_option( 'not*a*valid*value' ); | ||
|
|
||
| $connected_proxy_url->register(); | ||
|
|
||
| $this->assertEquals( 'not*a*valid*value', $connected_proxy_url->get(), 'Getter should leave a value that fails to decode unchanged.' ); | ||
| } |
There was a problem hiding this comment.
When this happens in the plugin, what error message is surfaced? 🤔
There was a problem hiding this comment.
This assertion now expects that FALSE, renamed to test_get__returns_false_for_a_value_that_fails_to_decode.
| $connected_proxy_url = new Connected_Proxy_URL( $this->options ); | ||
| $connected_proxy_url->register(); | ||
|
|
||
| // Save the option directly, as an update on the options screen would. |
There was a problem hiding this comment.
The built-in WordPress "All Settings" screen, wp-admin/options.php. I've removed that test and the sanitize callback, so that path doesn't apply anymore.
| $this->options->get( Connected_Proxy_URL::OPTION ), | ||
| 'Sanitize callback should encode a value that update_option saves.' | ||
| ); | ||
| $this->assertEquals( 'https://example.com/', $connected_proxy_url->get(), 'Getter should return the plain URL after a direct save.' ); |
There was a problem hiding this comment.
When is this a valid scenario? Users shouldn't be arbitrarily editing our settings…
There was a problem hiding this comment.
Yeah, it's not really a valid scenario! 👍🏻 I've removed the test.
| $this->assertFalse( $connected_proxy_url->matches_url( 'https://new-site.example.com' ), "Stored URL shouldn't match the new URL, so Site Kit detects the change." ); | ||
| } | ||
|
|
||
| public function test_matches_url__flags_a_url_change_after_a_database_search_and_replace() { |
There was a problem hiding this comment.
This isn't so much simulating a search-and-replace as a general home_url change, which a search-and-replace would include but is a subset of home URL changes.
Let's update the test name and comments/text to reflect this. It's fine to be more general in this naming. 🙂
tofumatt
left a comment
There was a problem hiding this comment.
Let's assume the value is encoded going forward, see: https://fueled.slack.com/archives/C0788NZMLF2/p1784807108217289
We don't need to account for an unencoded value after the migration, so this can be simplified. I've updated the issue's ACs accordingly.
Summary
Addresses issue:
Relevant technical choices
set(), so a value written straight to the option, as the options screen writes it, still stores encoded.PR Author Checklist
Do not alter or remove anything below. The following sections will be managed by moderators only.
Code Reviewer Checklist
Merge Reviewer Checklist